package com.jd.platform.sample4.util;

import com.jd.platform.hotkey.client.callback.JdHotKeyStore;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author wuweifeng wrote on 2020-02-21
 * @version 1.0
 */
@Component
public class Cache {
    // JdHotKeyStore是Caffeine为缓存。存到JVM 的内存中了，如果有需要则弄多一个redis的bean代替其进行读写
    // JdHotKeyStore的isHotKey和getValue方法是执行完对应逻辑才计数   ；  get不计数
    // JdHotKeyStore的计数是  不是hotkey才计数 以及 hotkey还有1秒过期才计数  注意：是hotkey时不到最后1秒不计数

    @Resource
    private RedisTemplate<String, String> redisTemplate;
//    @Resource
//    private StringRedisTemplate stringRedisTemplate;

    //单独查询（只是普通的获取值）
    public String getFromRedis(String key) {
        return redisTemplate.opsForValue().get(key); //获取普通key
    }

    //单独查询（走JdHotKeyStore计数的获取值）
    public String get(String key) {
        Object object = JdHotKeyStore.getValue(key); //是hotkey才获取到值；不管是不是hotkey都进行计数
        if (object != null) { //如果已经缓存过了，证明是hotkey
            System.out.println(key+"此时是hotkey");
            return object.toString();
        }
        System.out.println(key+"此时不是hotkey");
        String value = redisTemplate.opsForValue().get(key); //获取普通key

        //这里为空，可能有两个原因
        //redisTemplate没有，数据库有
        //该key不存在即没有存值时，被恶意访问了多次，为避免缓存穿透，设置值为""
        if(value==null) {
            System.out.println(key+"此时不存在于redis");
            return null; //从数据库尝试取值
        }
        System.out.println(key+"此时存在于redis");
        JdHotKeyStore.smartSet(key, value); //是热值才赋值为hotkey

        return value;
    }

    //单独查询（走JdHotKeyStore计数的获取值）
    public String[] gets(List<String> keys) {
        String[] values = new String[keys.size()];
        List<String> pre=new ArrayList<>(); //JdHotKeyStore查不到等待redis批量查询的key集合
        List<Integer> count=new ArrayList<>(); //等待redis批量查询的key的编号集合
        List<String> pre2=new ArrayList<>(); //与pre中key对应的value集合
//        List<String> pre3=new ArrayList<>(); //redis也查不到等待数据库查询的key集合（不需要，values哪里为null就通过数据库查）
        for(int i=0;i<keys.size();i++) {
            Object object = JdHotKeyStore.getValue(keys.get(i)); //是hotkey才获取到值；不管是不是hotkey都进行计数
            if (object != null) { //如果已经缓存过了，证明是hotkey
                System.out.println(keys.get(i)+"此时是hotkey");
                values[i] = object.toString();
            } else {
                System.out.println(keys.get(i)+"此时不是hotkey");
                pre.add(keys.get(i));
                count.add(i);
            }
        }

        if(pre.size()>0) {
            pre2 = redisTemplate.opsForValue().multiGet(pre); //获取普通key
            for(int i=0;i<count.size();i++) {
                int j=count.get(i);
                values[j]=pre2.get(i);
                if(values[j]==null) {
                    System.out.println(keys.get(j)+"此时不存在于redis，停止遍历，去查数据库");
                    values=null;
                    break;
                }
                System.out.println(keys.get(j)+"此时存在于redis");
                JdHotKeyStore.smartSet(keys.get(j), values[j]); //是热值才赋值为hotkey
            }
        }

        //这里values中有些为空，可能有两个原因
        //redisTemplate没有，数据库有
        //该key不存在即没有存值时，被恶意访问了多次，为避免缓存穿透，设置值为""
        return values;
    }

    //单独赋值（这里可能是添加，也可能是更新）
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value); //添加只执行了这个，因为一开始不是hotkey；更新也执行这个
        JdHotKeyStore.smartSet(key, value); //更新时如果是hotkey，则JdHotKeyStore也要跟着改
    }

    //批量赋值（这里可能是添加，也可能是更新）
    public void sets(List<String> keys, Object values) {
        Map<String, String> map = new HashMap<>();
        if(values instanceof List) {
            List<String> list = (List<String>) values;
            for (int i = 0; i < keys.size(); i++) {
                map.put(keys.get(i), list.get(i));
            }
            redisTemplate.opsForValue().multiSet(map); //添加只执行了这个，因为一开始不是hotkey；更新也执行这个

            for (int i = 0; i < keys.size(); i++) {
                JdHotKeyStore.smartSet(keys.get(i), list.get(i)); //更新时如果是hotkey，则JdHotKeyStore也要跟着改
            }
//        } else if(values instanceof String) {
        } else {
            String value = (String) values;
            for(String key : keys) {
                map.put(key, value);
            }
            redisTemplate.opsForValue().multiSet(map); //添加只执行了这个，因为一开始不是hotkey；更新也执行这个

            for (int i = 0; i < keys.size(); i++) {
                JdHotKeyStore.smartSet(keys.get(i), value); //更新时如果是hotkey，则JdHotKeyStore也要跟着改
            }
        }
    }

//    //如果可预见的知道某hotkey将很少被访问，则只执行这个（没必要，JdHotKeyStore有自己的一套hotkey转普通key的逻辑）
//    public void beCold(String key) {
//        JdHotKeyStore.remove(key);
//    }

//    //如果可预见的知道某hkey将很多被访问成为hotkey，则执行这个（没必要，JdHotKeyStore的存在就是为了应对突发的高访问）
//    public void beHot(String key, String value) {
//        JdHotKeyStore.forceSet(key,value);
//    }

    //删除redis的数据，此时为避免幻读，需要将JdHotKeyStore中对应的hotkey一起删了
    public void remove(String key) {
        redisTemplate.delete(key);
        JdHotKeyStore.remove(key);
    }

    public void removes(List<String> keys) {
        redisTemplate.delete(keys);
        for(int i=0;i<keys.size();i++) {
            JdHotKeyStore.remove(keys.get(i));
        }
    }

//    //查找命名符合自定规则（前缀*后缀）的key（只测试使用，生产环境最好不用）（用AttributeUtil.getAttributeValue更实用高效）
//    //https://blog.csdn.net/zhoudingding/article/details/108115502
//    public Set<String> getKeysByPattern(String pattern) {
////        return redisTemplate.keys(pattern); //没有优化
//
//        Set<String> keys = new LinkedHashSet<>();
//        redisTemplate.execute((RedisConnection connection) -> {
//            try (Cursor<byte[]> cursor = connection.scan( //使用SCAN命令扫描符合条件的key
//                    ScanOptions.scanOptions()
//                            .count(Long.MAX_VALUE) // 每次扫描的数量（非精确值，可调整）
//                            .match(pattern) // 匹配规则
//                            .build()
//            )) {
//                cursor.forEachRemaining(item -> { // 遍历游标，收集所有符合条件的key
//                    keys.add(RedisSerializer.string().deserialize(item));
//                });
//                return null;
//            } catch (Exception e) {
//                throw new RuntimeException("扫描Redis key失败", e);
//            } finally {
//                return keys;
//            }
//        });
//        return keys;
//    }
}
